summaryrefslogtreecommitdiff
path: root/app/api/auth/[...nextauth]/route.ts
diff options
context:
space:
mode:
Diffstat (limited to 'app/api/auth/[...nextauth]/route.ts')
-rw-r--r--app/api/auth/[...nextauth]/route.ts85
1 files changed, 52 insertions, 33 deletions
diff --git a/app/api/auth/[...nextauth]/route.ts b/app/api/auth/[...nextauth]/route.ts
index 2b168746..e059377c 100644
--- a/app/api/auth/[...nextauth]/route.ts
+++ b/app/api/auth/[...nextauth]/route.ts
@@ -1,4 +1,3 @@
-// auth/config.ts - 업데이트된 NextAuth 설정
import NextAuth, {
NextAuthOptions,
Session,
@@ -14,16 +13,15 @@ import { verifyOtpTemp } from '@/lib/users/verifyOtp'
import { getSecuritySettings } from '@/lib/password-policy/service'
import { verifySmsToken } from '@/lib/users/auth/passwordUtil'
import { SessionRepository } from '@/lib/users/session/repository'
-import { loginSessions } from '@/db/schema'
// 인증 방식 타입 정의
type AuthMethod = 'otp' | 'email' | 'sgips' | 'saml'
-// 모듈 보강 선언 (기존과 동일)
+// 모듈 보강 선언 - ID를 string으로 통일
declare module "next-auth" {
interface Session {
user: {
- id: string
+ id: string // number → string으로 변경
name?: string | null
email?: string | null
image?: string | null
@@ -33,12 +31,12 @@ declare module "next-auth" {
reAuthTime?: number | null
authMethod?: AuthMethod
sessionExpiredAt?: number | null
- dbSessionId?: string | null // DB 세션 ID 추가
+ dbSessionId?: string | null
}
}
interface User {
- id: string
+ id: string // number → string으로 변경
imageUrl?: string | null
companyId?: number | null
techCompanyId?: number | null
@@ -51,7 +49,7 @@ declare module "next-auth" {
declare module "next-auth/jwt" {
interface JWT {
- id?: string
+ id?: string // 이미 string이므로 그대로
imageUrl?: string | null
companyId?: number | null
techCompanyId?: number | null
@@ -63,6 +61,15 @@ declare module "next-auth/jwt" {
}
}
+// 타입 변환 헬퍼 함수들
+function ensureString(value: string | number): string {
+ return String(value)
+}
+
+function ensureNumber(value: string | number): number {
+ return typeof value === 'string' ? parseInt(value, 10) : value
+}
+
// 보안 설정 캐시 (기존과 동일)
let securitySettingsCache: {
data: any | null
@@ -71,7 +78,7 @@ let securitySettingsCache: {
} = {
data: null,
lastFetch: 0,
- ttl: 5 * 60 * 1000 // 5분 캐시
+ ttl: 5 * 60 * 1000
}
async function getCachedSecuritySettings() {
@@ -85,7 +92,7 @@ async function getCachedSecuritySettings() {
} catch (error) {
console.error('Failed to fetch security settings:', error)
securitySettingsCache.data = {
- sessionTimeoutMinutes: 480 // 8시간 기본값
+ sessionTimeoutMinutes: 480
}
}
}
@@ -111,7 +118,7 @@ function getClientIP(req: any): string {
export const authOptions: NextAuthOptions = {
providers: [
- // OTP 로그인 (기존 유지)
+ // OTP 로그인 - 타입 에러 수정
CredentialsProvider({
id: 'credentials-otp',
name: 'OTP',
@@ -130,8 +137,9 @@ export const authOptions: NextAuthOptions = {
const securitySettings = await getCachedSecuritySettings()
const reAuthTime = Date.now()
+ // 반환 객체의 id를 string으로 변환
return {
- id: String(user.id ?? email ?? "dts"),
+ id: ensureString(user.id), // ✅ string으로 변환
email: user.email,
imageUrl: user.imageUrl ?? null,
name: user.name,
@@ -144,12 +152,12 @@ export const authOptions: NextAuthOptions = {
},
}),
- // MFA 완료 후 최종 인증 (DB 연동 버전)
+ // MFA 완료 후 최종 인증 - 타입 에러 수정
CredentialsProvider({
id: 'credentials-mfa',
name: 'MFA Verification',
credentials: {
- userId: { label: 'User ID', type: 'text' },
+ userId: { label: 'User ID', type: 'text' }, // number → text로 변경
smsToken: { label: 'SMS Token', type: 'text' },
tempAuthKey: { label: 'Temp Auth Key', type: 'text' },
},
@@ -159,28 +167,29 @@ export const authOptions: NextAuthOptions = {
return null
}
+ // userId를 number로 변환하여 DB 조회
+ const numericUserId = ensureNumber(credentials.userId)
+ const user = await getUserById(numericUserId)
+ if (!user) {
+ console.error('User not found after MFA verification')
+ return null
+ }
+
try {
// DB에서 임시 인증 정보 확인
const tempAuth = await SessionRepository.getTempAuthSession(credentials.tempAuthKey)
- if (!tempAuth || tempAuth.userId !== credentials.userId) {
+ if (!tempAuth || ensureNumber(tempAuth.userId) !== user.id) {
console.error('Temp auth expired or not found')
return null
}
// SMS 토큰 검증
- const smsVerificationResult = await verifySmsToken(Number(credentials.userId), credentials.smsToken)
+ const smsVerificationResult = await verifySmsToken(user.id, credentials.smsToken)
if (!smsVerificationResult || !smsVerificationResult.success) {
console.error('SMS token verification failed')
return null
}
- // 사용자 정보 조회
- const user = await getUserById(Number(credentials.userId))
- if (!user) {
- console.error('User not found after MFA verification')
- return null
- }
-
// 임시 인증 정보를 사용됨으로 표시
await SessionRepository.markTempAuthSessionAsUsed(credentials.tempAuthKey)
@@ -194,7 +203,7 @@ export const authOptions: NextAuthOptions = {
const userAgent = req.headers?.['user-agent']
const dbSession = await SessionRepository.createLoginSession({
- userId: String(user.id),
+ userId: user.id, // number로 전달
ipAddress,
userAgent,
authMethod: tempAuth.authMethod,
@@ -203,8 +212,9 @@ export const authOptions: NextAuthOptions = {
console.log(`MFA completed for user ${user.email} (${tempAuth.authMethod})`)
+ // 반환 객체의 id를 string으로 변환
return {
- id: String(user.id),
+ id: ensureString(user.id), // ✅ string으로 변환
email: user.email,
imageUrl: user.imageUrl ?? null,
name: user.name,
@@ -257,7 +267,7 @@ export const authOptions: NextAuthOptions = {
session: {
strategy: 'jwt',
- maxAge: 30 * 24 * 60 * 60, // 30일
+ maxAge: 30 * 24 * 60 * 60,
},
callbacks: {
@@ -268,7 +278,7 @@ export const authOptions: NextAuthOptions = {
// 최초 로그인 시 (MFA 완료 후)
if (user) {
const reAuthTime = Date.now()
- token.id = user.id
+ token.id = user.id // ✅ 이제 둘 다 string 타입
token.email = user.email
token.name = user.name
token.companyId = user.companyId
@@ -288,8 +298,8 @@ export const authOptions: NextAuthOptions = {
try {
const dbSession = await SessionRepository.createLoginSession({
- userId: token.id,
- ipAddress: '0.0.0.0', // SAML의 경우 IP 추적 제한적
+ userId: ensureNumber(token.id), // string을 number로 변환하여 DB에 저장
+ ipAddress: '0.0.0.0',
authMethod: 'saml',
sessionExpiredAt,
})
@@ -346,7 +356,7 @@ export const authOptions: NextAuthOptions = {
if (token) {
session.user = {
- id: token.id as string,
+ id: token.id as string, // ✅ string으로 일관성 유지
email: token.email as string,
name: token.name as string,
domain: token.domain as string,
@@ -386,14 +396,16 @@ export const authOptions: NextAuthOptions = {
// 이미 MFA에서 DB 세션이 생성된 경우가 아니라면 여기서 생성
if (account?.provider !== 'credentials-mfa' && user.id) {
try {
+ const numericUserId = ensureNumber(user.id) // string을 number로 변환
+
// 기존 활성 세션 확인
- const existingSession = await SessionRepository.getActiveSessionByUserId(user.id)
+ const existingSession = await SessionRepository.getActiveSessionByUserId(numericUserId)
if (!existingSession) {
const sessionExpiredAt = new Date(Date.now() + (securitySettings.sessionTimeoutMinutes * 60 * 1000))
await SessionRepository.createLoginSession({
- userId: user.id,
- ipAddress: '0.0.0.0', // signIn 이벤트에서는 IP 접근 제한적
+ userId: numericUserId,
+ ipAddress: '0.0.0.0',
authMethod: user.authMethod || 'unknown',
sessionExpiredAt,
})
@@ -415,8 +427,15 @@ export const authOptions: NextAuthOptions = {
await SessionRepository.logoutSession(dbSessionId)
} else if (userId) {
// dbSessionId가 없는 경우 사용자의 모든 활성 세션 로그아웃
- await SessionRepository.logoutAllUserSessions(userId)
+ const numericUserId = ensureNumber(userId) // string을 number로 변환
+ await SessionRepository.logoutAllUserSessions(numericUserId)
}
}
}
}
+
+
+const handler = NextAuth(authOptions)
+
+// ✅ 핵심: 반드시 GET, POST를 named export로 내보내야 함
+export { handler as GET, handler as POST } \ No newline at end of file